<template>
  <div class="tutorial-page">
    <div class="tutorial-header">
      <h1>样式绑定</h1>
      <p>学习动态绑定class和style的多种方式</p>
    </div>

    <div class="tutorial-content">
      <div class="section">
        <h2>绑定 HTML Class</h2>
        <p>操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是 attribute，所以我们可以用 v-bind 处理它们：只需要通过表达式计算出字符串结果即可。不过，字符串拼接麻烦且易错。因此，在将 v-bind 用于 class 和 style 时，Vue.js 做了专门的增强。表达式结果的类型除了字符串之外，还可以是对象或数组。</p>
        
        <h3>对象语法</h3>
        <pre><code class="language-html">&lt;div :class="{ active: isActive }"&gt;&lt;/div&gt;</code></pre>
        <pre><code class="language-javascript">data() {
  return {
    isActive: true
  }
}</code></pre>
        <p>上面的语法表示 active 这个 class 存在与否将取决于数据 property isActive 的真假。</p>
        
        <p>你可以在对象中传入更多字段来动态切换多个 class。此外，:class 指令也可以与普通的 class attribute 共存。</p>
        <pre><code class="language-html">&lt;div
  class="static"
  :class="{ active: isActive, 'text-danger': hasError }"
&gt;&lt;/div&gt;</code></pre>
        <pre><code class="language-javascript">data() {
  return {
    isActive: true,
    hasError: false
  }
}</code></pre>
        
        <p>绑定的数据对象不必内联定义在模板里：</p>
        <pre><code class="language-html">&lt;div :class="classObject"&gt;&lt;/div&gt;</code></pre>
        <pre><code class="language-javascript">data() {
  return {
    classObject: {
      active: true,
      'text-danger': false
    }
  }
}</code></pre>
        
        <p>我们也可以在这里绑定一个返回对象的计算属性：</p>
        <pre><code class="language-html">&lt;div :class="classObject"&gt;&lt;/div&gt;</code></pre>
        <pre><code class="language-javascript">data() {
  return {
    isActive: true,
    error: null
  }
},
computed: {
  classObject() {
    return {
      active: this.isActive && !this.error,
      'text-danger': this.error && this.error.type === 'fatal'
    }
  }
}</code></pre>
      </div>

      <div class="section">
        <h2>数组语法</h2>
        <p>我们可以把一个数组传给 :class，以应用一个 class 列表：</p>
        <pre><code class="language-html">&lt;div :class="[activeClass, errorClass]"&gt;&lt;/div&gt;</code></pre>
        <pre><code class="language-javascript">data() {
  return {
    activeClass: 'active',
    errorClass: 'text-danger'
  }
}</code></pre>
        
        <p>如果你也想根据条件切换列表中的 class，可以用三元表达式：</p>
        <pre><code class="language-html">&lt;div :class="[isActive ? activeClass : '', errorClass]"&gt;&lt;/div&gt;</code></pre>
        
        <p>这样写将始终添加 errorClass，但是只有在 isActive 为真时才添加 activeClass。</p>
        
        <p>不过，当有多个条件 class 时这样写有些繁琐。所以在数组语法中也可以使用对象语法：</p>
        <pre><code class="language-html">&lt;div :class="[{ active: isActive }, errorClass]"&gt;&lt;/div&gt;</code></pre>
      </div>

      <div class="section">
        <h2>用在组件上</h2>
        <p>当在一个自定义组件上使用 class property 时，这些 class 将被添加到该组件的根元素上面。这个元素上已经存在的 class 不会被覆盖。</p>
        
        <pre><code class="language-html">&lt;!-- 子组件 --&gt;
&lt;!-- my-component --&gt;
&lt;p class="foo bar"&gt;Hi!&lt;/p&gt;</code></pre>
        <pre><code class="language-html">&lt;!-- 父组件使用 --&gt;
&lt;my-component class="baz boo"&gt;&lt;/my-component&gt;</code></pre>
        <p>HTML 将被渲染为:</p>
        <pre><code class="language-html">&lt;p class="foo bar baz boo"&gt;Hi!&lt;/p&gt;</code></pre>
        
        <p>对于带数据绑定的 class 也是如此：</p>
        <pre><code class="language-html">&lt;my-component :class="{ active: isActive }"&gt;&lt;/my-component&gt;</code></pre>
        <p>当 isActive 为真时，HTML 将被渲染成为：</p>
        <pre><code class="language-html">&lt;p class="foo bar active"&gt;Hi!&lt;/p&gt;</code></pre>
      </div>

      <div class="section">
        <h2>绑定内联样式</h2>
        
        <h3>对象语法</h3>
        <p>:style 的对象语法十分直观——看着非常像 CSS，但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case，记得用引号括起来) 来命名：</p>
        <pre><code class="language-html">&lt;div :style="{ color: activeColor, fontSize: fontSize + 'px' }"&gt;&lt;/div&gt;</code></pre>
        <pre><code class="language-javascript">data() {
  return {
    activeColor: 'red',
    fontSize: 30
  }
}</code></pre>
        
        <p>直接绑定到一个样式对象通常更好，这会让模板更清晰：</p>
        <pre><code class="language-html">&lt;div :style="styleObject"&gt;&lt;/div&gt;</code></pre>
        <pre><code class="language-javascript">data() {
  return {
    styleObject: {
      color: 'red',
      fontSize: '13px'
    }
  }
}</code></pre>
        
        <h3>数组语法</h3>
        <p>:style 的数组语法可以将多个样式对象应用到同一个元素上：</p>
        <pre><code class="language-html">&lt;div :style="[baseStyles, overridingStyles]"&gt;&lt;/div&gt;</code></pre>
        
        <h3>自动添加前缀</h3>
        <p>当 v-bind:style 使用需要添加浏览器引擎前缀的 CSS property 时，如 transform，Vue.js 会自动侦测并添加相应的前缀。</p>
        
        <h3>多重值</h3>
        <p>可以为 style 绑定中的 property 提供一个包含多个值的数组，常用于提供多个带前缀的值：</p>
        <pre><code class="language-html">&lt;div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"&gt;&lt;/div&gt;</code></pre>
        <p>这样写只会渲染数组中最后一个被浏览器支持的值。在本例中，如果浏览器支持不带浏览器前缀的 flexbox，那么就只会渲染 display: flex。</p>
      </div>

      <div class="section">
        <h2>实际应用示例</h2>
        
        <h3>动态主题切换</h3>
        <pre><code class="language-html">&lt;div :class="themeClass"&gt;
  &lt;button @click="toggleTheme"&gt;切换主题&lt;/button&gt;
  &lt;p&gt;当前主题: {{ currentTheme }}&lt;/p&gt;
&lt;/div&gt;</code></pre>
        <pre><code class="language-javascript">data() {
  return {
    currentTheme: 'light'
  }
},
computed: {
  themeClass() {
    return `theme-${this.currentTheme}`
  }
},
methods: {
  toggleTheme() {
    this.currentTheme = this.currentTheme === 'light' ? 'dark' : 'light'
  }
}</code></pre>
        
        <h3>表单验证状态</h3>
        <pre><code class="language-html">&lt;input 
  v-model="email" 
  :class="emailClasses"
  placeholder="请输入邮箱"
&gt;
&lt;span v-if="emailError" class="error-message"&gt;{{ emailError }}&lt;/span&gt;</code></pre>
        <pre><code class="language-javascript">data() {
  return {
    email: '',
    emailError: ''
  }
},
computed: {
  emailClasses() {
    return {
      'input-field': true,
      'valid': this.isValidEmail && !this.emailError,
      'invalid': this.emailError
    }
  },
  isValidEmail() {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.email)
  }
},
watch: {
  email(newVal) {
    if (!this.isValidEmail && newVal) {
      this.emailError = '请输入有效的邮箱地址'
    } else {
      this.emailError = ''
    }
  }
}</code></pre>
      </div>
    </div>
  </div>
</template>

<script setup>
// 页面逻辑可以在这里添加
</script>

<style scoped>
@import '@/assets/tutorial-styles.css';
</style>